#ifndef data_exchange_h__
#define data_exchange_h__

#include "winboost.hpp"
#include <boost/logic/tribool.hpp>

namespace winboost {
   namespace windows {

enum transfer_direction
{
   to_variable,
   to_control,
};

class data_exchange
{
   typedef boost::unordered_map<UINT, boost::any> var_map_t;
   typedef boost::unordered_map<UINT, int*> var_buttons_t;
   typedef boost::unordered_map<UINT, boost::logic::tribool*> var_checkbox_t;
   typedef boost::unordered_map<UINT, resources::win_handle*> ctrl_map_t;

public:
   template<typename _T> void set_edit_variable(UINT id, _T var);
   void set_radio_variable(UINT id, int* pvar);
   void set_checkbox_variable(UINT id, boost::logic::tribool* pvar);
   void set_control(UINT id, resources::win_handle* pwnd);

   void transfer_to(HWND hparent);
   void transfer_from(HWND hparent);

private:
   var_map_t m_edit_variables;
   var_buttons_t m_radio_variables;
   var_checkbox_t m_checkbox_variables;
   ctrl_map_t m_controls;

   void transfer_to_var(var_map_t::value_type& t, HWND hparent);
   void transfer_from_var(var_map_t::value_type& t, HWND hparent);
   void transfer_radio_var(var_buttons_t::value_type& t, HWND hparent, bool to);
   void transfer_checkbox_var(var_checkbox_t::value_type& t, HWND hparent, bool to);
   void subclass_controls(ctrl_map_t::value_type& t, HWND hparent);

   template<typename _T> void convert_numerics(boost::any& val, const TCHAR* buffer);
   template<typename _T> void convert_strings(boost::any& val, const TCHAR* buffer);
   template<typename _T> void strings_to_control(boost::any& val, HWND hctrl);
   template<typename _T> void stdstr_to_control(boost::any& val, HWND hctrl);
};

//-------------------------------------
// Implementation.
//-------------------------------------
template<typename _T>
void data_exchange::set_edit_variable(UINT id, _T var)
{
   // Parameter must be a pointer.
   const std::tr1::true_type& var_type = std::tr1::is_pointer<_T>();
   m_edit_variables[id] = var;
}

inline void data_exchange::transfer_to(HWND hparent)
{
   std::for_each(m_edit_variables.begin(), m_edit_variables.end(), 
      boost::bind(&data_exchange::transfer_to_var, this, _1, hparent));

   std::for_each(m_radio_variables.begin(), m_radio_variables.end(), 
      boost::bind(&data_exchange::transfer_radio_var, this, _1, hparent, true));

   std::for_each(m_checkbox_variables.begin(), m_checkbox_variables.end(), 
      boost::bind(&data_exchange::transfer_checkbox_var, this, _1, hparent, true));
}

inline void data_exchange::transfer_from(HWND hparent)
{
   std::for_each(m_edit_variables.begin(), m_edit_variables.end(), 
      boost::bind(&data_exchange::transfer_from_var, this, _1, hparent));

   std::for_each(m_radio_variables.begin(), m_radio_variables.end(), 
      boost::bind(&data_exchange::transfer_radio_var, this, _1, hparent, false));

   std::for_each(m_checkbox_variables.begin(), m_checkbox_variables.end(), 
      boost::bind(&data_exchange::transfer_checkbox_var, this, _1, hparent, false));

   std::for_each(m_controls.begin(), m_controls.end(), 
      boost::bind(&data_exchange::subclass_controls, this, _1, hparent));
}

inline void data_exchange::transfer_to_var(var_map_t::value_type& t, HWND hparent)
{
   UINT id = t.first;
   boost::any var = t.second;

   HWND hwnd = ::GetDlgItem(hparent, id);
   assert(hwnd);

   int len = ::GetWindowTextLength(hwnd);

   boost::scoped_array<TCHAR> buffer(new TCHAR[len+1]);
   ::GetWindowText(hwnd, buffer.get(), len+1);

   if(var.type() == typeid(std::wstring*))
      convert_strings<std::wstring>(var, buffer.get());
   else if(var.type() == typeid(std::string*))
      convert_strings<std::string>(var, buffer.get());
   else if(var.type() == typeid(long*))
      convert_numerics<long>(var, buffer.get());
   else if(var.type() == typeid(unsigned long*))
      convert_numerics<unsigned long>(var, buffer.get());
   else if(var.type() == typeid(long long*))
      convert_numerics<long long>(var, buffer.get());
   else if(var.type() == typeid(unsigned long long*))
      convert_numerics<unsigned long long>(var, buffer.get());
   else if(var.type() == typeid(int*))
      convert_numerics<int>(var, buffer.get());
   else if(var.type() == typeid(unsigned int*))
      convert_numerics<unsigned int>(var, buffer.get());
   else if(var.type() == typeid(short*))
      convert_numerics<short>(var, buffer.get());
   else if(var.type() == typeid(unsigned short*))
      convert_numerics<unsigned short>(var, buffer.get());
   else if(var.type() == typeid(double*))
      convert_numerics<double>(var, buffer.get());
   else if(var.type() == typeid(float*))
      convert_numerics<float>(var, buffer.get());
   else if(var.type() == typeid(TCHAR*))
      convert_numerics<TCHAR>(var, buffer.get());
   else
   {
      // this type is not supported
      assert(false);
   }
}

template<typename _T> 
void data_exchange::convert_numerics(boost::any& val, const TCHAR* buffer)
{
   u_stringstream uss;
   uss << buffer;
   _T* var = boost::any_cast<_T*>(val);
   uss >> *var;
}

template<typename _T> 
void data_exchange::convert_strings(boost::any& val, const TCHAR* buffer)
{
   u_stringstream uss;
   uss << buffer;
   _T* p = boost::any_cast<_T*>(val);
   u_string s = uss.str();
   p->assign(s.begin(), s.end());
}

inline void data_exchange::transfer_from_var(var_map_t::value_type& t, HWND hparent)
{
   UINT id = t.first;
   boost::any var = t.second;

   HWND hctrl = ::GetDlgItem(hparent, id);
   assert(hctrl);

   if(var.type() == typeid(std::wstring*))
      strings_to_control<std::wstring>(var, hctrl);
   else if(var.type() == typeid(std::string*))
      stdstr_to_control<std::string>(var, hctrl);
   else if(var.type() == typeid(long*))
      strings_to_control<long>(var, hctrl);
   else if(var.type() == typeid(unsigned long*))
      strings_to_control<unsigned long>(var, hctrl);
   else if(var.type() == typeid(long long*))
      strings_to_control<long long>(var, hctrl);
   else if(var.type() == typeid(unsigned long long*))
      strings_to_control<unsigned long long>(var, hctrl);
   else if(var.type() == typeid(int*))
      strings_to_control<int>(var, hctrl);
   else if(var.type() == typeid(unsigned int*))
      strings_to_control<unsigned int>(var, hctrl);
   else if(var.type() == typeid(short*))
      strings_to_control<short>(var, hctrl);
   else if(var.type() == typeid(unsigned short*))
      strings_to_control<unsigned short>(var, hctrl);
   else if(var.type() == typeid(double*))
      strings_to_control<double>(var, hctrl);
   else if(var.type() == typeid(float*))
      strings_to_control<float>(var, hctrl);
   else if(var.type() == typeid(TCHAR*))
      strings_to_control<TCHAR>(var, hctrl);
   else
   {
      // this type is not supported
      assert(false);
   }   
}

template<typename _T> 
void data_exchange::strings_to_control(boost::any& val, HWND hctrl)
{
   _T* p = boost::any_cast<_T*>(val);
   u_stringstream uss;
   uss << *p;
   u_string s = uss.str();

   int len = ::GetWindowTextLength(hctrl);
   boost::scoped_array<TCHAR> buffer(new TCHAR[len+1]);
   ::GetWindowText(hctrl, buffer.get(), len+1);

   u_string old = buffer.get();
   if(old != s)
      ::SetWindowText(hctrl, s.c_str());
}

//-------------------------------------
// Special case std::string version for unicode
// builds
//-------------------------------------
template<typename _T> 
void data_exchange::stdstr_to_control(boost::any& val, HWND hctrl)
{
   _T* p = boost::any_cast<_T*>(val);
   std::stringstream uss;
   uss << *p;

   std::string s1 = uss.str();
   u_string s;
   s.assign(s1.begin(), s1.end());

   int len = ::GetWindowTextLength(hctrl);
   boost::scoped_array<TCHAR> buffer(new TCHAR[len+1]);
   ::GetWindowText(hctrl, buffer.get(), len+1);

   u_string old = buffer.get();
   if(old != s)
      ::SetWindowText(hctrl, s.c_str());
}

inline void data_exchange::set_radio_variable(UINT id, int* pvar)
{
   m_radio_variables[id] = pvar;
}

inline void data_exchange::transfer_radio_var(var_buttons_t::value_type& t, HWND hparent, bool to)
{
   UINT id = t.first;
   int* pval = t.second;

   HWND hctrl = ::GetDlgItem(hparent, id);
   assert(hctrl);

   assert(::GetWindowLong(hctrl, GWL_STYLE) & WS_GROUP);
   assert(::SendMessage(hctrl, WM_GETDLGCODE, 0, 0L) & DLGC_RADIOBUTTON);

   int radio = 0;

   do 
   {
      if(::SendMessage(hctrl, WM_GETDLGCODE, 0, 0L) & DLGC_RADIOBUTTON)
      {
         if(to)
         {
            if (::SendMessage(hctrl, BM_GETCHECK, 0, 0L) != 0)
            {
               //assert(*pval == -1);    // only set once
               *pval = radio;
            }
         }
         else
         {
            // select button
            ::SendMessage(hctrl, BM_SETCHECK, (radio == *pval), 0L);
         }
         radio++;
      }
      else
      {
         // TRACE(traceAppMsg, 0, "Warning: skipping non-radio button in group.\n");
      }
      hctrl = ::GetWindow(hctrl, GW_HWNDNEXT);
   } while (hctrl != NULL && !(GetWindowLong(hctrl, GWL_STYLE) & WS_GROUP));
}

inline void data_exchange::set_checkbox_variable(UINT id, boost::logic::tribool* pvar)
{
   m_checkbox_variables[id] = pvar;
}

inline void data_exchange::transfer_checkbox_var(var_checkbox_t::value_type& t, HWND hparent, bool to)
{
   UINT id = t.first;
   boost::logic::tribool* pval = t.second;

   HWND hctrl = ::GetDlgItem(hparent, id);
   assert(hctrl);

   if(to)
   {
      int value = (int)::SendMessage(hctrl, BM_GETCHECK, 0, 0L);
      if(value == 0)
         *pval = false;
      else if(value == 1)
         *pval = true;
      else
         *pval = boost::logic::indeterminate;
   }
   else
   {
      int value = 0;

      if(*pval == true)
         value = 1;
      else if(*pval == false)
         value = 0;
      else
         value = 2;

      ::SendMessage(hctrl, BM_SETCHECK, (WPARAM)value, 0L);
   }
}

inline void data_exchange::set_control(UINT id, resources::win_handle* pwnd)
{
   m_controls[id] = pwnd;
}

inline void data_exchange::subclass_controls(ctrl_map_t::value_type& t, HWND hparent)
{
   UINT id = t.first;
   resources::win_handle* pwnd = t.second;

   // control already created or attached
   if(pwnd->handle() && ::IsWindow(pwnd->handle()))
      return;

   HWND hctrl = ::GetDlgItem(hparent, id);
   assert(hctrl);

   pwnd->subclass_window(hctrl);
}

   }
}


#endif // data_exchange_h__